package com.gitee.apanlh.util.algorithm.encrypt.symmetric;

import com.gitee.apanlh.util.algorithm.encrypt.AlgorithmMode;
import com.gitee.apanlh.util.algorithm.encrypt.AlgorithmPadding;
import com.gitee.apanlh.util.algorithm.encrypt.CipherMode;
import com.gitee.apanlh.util.algorithm.encrypt.Encrypt;
import com.gitee.apanlh.util.base.Eq;
import com.gitee.apanlh.util.base.StringUtils;
import com.gitee.apanlh.util.encode.Base64Type;
import com.gitee.apanlh.util.encode.Base64Utils;
import com.gitee.apanlh.util.encode.HexUtils;
import com.gitee.apanlh.util.encode.StrEncodeUtils;
import com.gitee.apanlh.util.valid.Assert;

import javax.crypto.Cipher;
import java.security.Provider;

/**
 * 	对称算法加密/解密抽象类
 * 	<br>默认实现基本功能
 * 	
 * 	@author Pan
 * 	@see	BouncyCastleSymmetricAbstract
 * 	@see    CustomSymmetricAbstract
 */
public abstract class SymmetricAbstract implements Encrypt {
	
	/** 默认16字节(128块长度) */
	public static final int BLOCK_SIZE = 16;
	/** 算法格式化字符串 */
	private static final String FORMAT_STR = "{}/{}/{}";
	
	/**	
	 * 	自定义加解密器
	 * 	<br>用于子类中实现指定算法
	 * 	
	 * 	@author Pan
	 * 	@param 	key			密钥
	 * 	@param 	iv			初始化向量
	 * 	@param 	cipherMode	加解密器模式
	 * 	@return	Cipher	
	 */
	public abstract Cipher initCipher(byte[] key, byte[] iv, CipherMode cipherMode);
	
	/**	
	 * 	自定义加解密器
	 * 	<br>用于子类中实现指定算法
	 * 	
	 * 	@author Pan
	 * 	@param 	key			密钥
	 * 	@param 	iv			初始化向量
	 * 	@param 	cipherMode	加解密器模式
	 * 	@return	Cipher	
	 */
	public abstract Cipher initCipher(String key, byte[] iv, CipherMode cipherMode);
	
	/**	
	 * 	自定义加解密器
	 * 	<br>用于子类中实现指定算法
	 * 	
	 * 	@author Pan
	 * 	@param 	key					密钥
	 * 	@param 	iv					初始化向量
	 * 	@param 	cipherMode			加解密器模式
	 * 	@param 	algorithmMode		算法模式
	 * 	@param 	algorithmPadding	算法填充
	 * 	@return	Cipher	
	 */
	public abstract Cipher initCipher(byte[] key, byte[] iv, CipherMode cipherMode, AlgorithmMode algorithmMode, AlgorithmPadding algorithmPadding);
	
	/**	
	 * 	自定义加解密器
	 * 	<br>用于子类中实现指定算法
	 * 	
	 * 	@author Pan
	 * 	@param 	key					密钥
	 * 	@param 	iv					初始化向量
	 * 	@param 	cipherMode			加解密器模式	 
	 * 	@param 	algorithmMode		算法模式
	 * 	@param 	algorithmPadding	算法填充
	 * 	@return	Cipher	
	 */
	public abstract Cipher initCipher(String key, byte[] iv, CipherMode cipherMode, AlgorithmMode algorithmMode, AlgorithmPadding algorithmPadding);
	
	/**
	 * 	抽象方法，加解密过程的最终执行方法
	 * 	<br>注意:加密时在途中会执行{@link #doPadding(byte[], int)}方法，解密时在途中执行{@link #doUnpadding(byte[], int)}方法
	 * 	<br>如果选择了特殊的填充模式如：NoPadding、Zero等时候默认16字节(128位填充)，否则将会按照默认块大小执行
	 * 	<br>如果想改变算法指定块大小请重写{@link #doFinal(CipherMode, byte[], int)}方法
	 * 	
	 * 	@author Pan
	 * 	@param 	cipherMode	加密器模式
	 * 	@param 	content		内容
	 * 	@return	byte[]
	 */
	public abstract byte[] doFinal(CipherMode cipherMode, byte[] content);
	
	/**
	 * 	抽象方法，加解密过程的最终执行方法
	 * 	<br>注意:加密时在途中会执行{@link #doPadding(byte[], int)}方法，解密时在途中执行{@link #doUnpadding(byte[], int)}方法
	 * 	<br>如果选择了特殊的填充模式如：NoPadding、Zero等将按照指定块大小执行
	 * 
	 * 	@author Pan
	 * 	@param 	cipherMode	加密器模式
	 * 	@param 	content		内容
	 * 	@param 	blockSize	指定块大小
	 * 	@return	byte[]
	 */
	public abstract byte[] doFinal(CipherMode cipherMode, byte[] content, int blockSize);
	
	/**	
	 * 	自定义填充方法
	 * 	<br>自定义块长度
	 * 
	 * 	@author Pan
	 * 	@param 	content		内容
	 * 	@param 	blockSize	块长度
	 * 	@return	byte[]
	 */
	public abstract byte[] doPadding(byte[] content, int blockSize);
	
	/**	
	 * 	自定义去除填充方法
	 * 	<br>自定义块长度
	 * 	
	 * 	@author Pan
	 * 	@param 	paddedContent	已填充内容
	 * 	@param 	blockSize		块长度
	 * 	@return byte[]
	 */
	public abstract byte[] doUnpadding(byte[] paddedContent, int blockSize);
	
	/**	
	 * 	获取Provider
	 * 	
	 * 	@author Pan
	 * 	@return	Provider
	 */
	public abstract Provider getProvider();
	
	/**	
	 * 	获取算法方式
	 * 	
	 * 	@author Pan
	 * 	@param 	symmetricType	对称算法类型
	 * 	@param 	mode			算法模式
	 * 	@param 	padding			算法填充模式
	 * 	@return	String
	 */
	public String getAlgorithm(SymmetricType symmetricType, AlgorithmMode mode, AlgorithmPadding padding) {
		return StringUtils.format(FORMAT_STR, symmetricType, mode, padding);
	}
	
	
	@Override
	public byte[] encrypt(byte[] content) {
		Assert.isNotEmpty(content);
		return doFinal(CipherMode.ENCRYPT, content);
	}
	
	@Override
	public byte[] encrypt(String content) {
		Assert.isNotEmpty(content);
		return doFinal(CipherMode.ENCRYPT, content.getBytes());
	}
	
	@Override
	public String encryptToHex(byte[] content) {
		Assert.isNotEmpty(content);
		return HexUtils.encode(doFinal(CipherMode.ENCRYPT, content));
	}
	
	@Override
	public String encryptToHex(byte[] content, boolean toLowerCase) {
		return HexUtils.encode(doFinal(CipherMode.ENCRYPT, content), toLowerCase);
	}
	
	@Override
	public String encryptToHex(String content) {
		Assert.isNotEmpty(content);
		return encryptToHex(content.getBytes());
	}
	
	@Override
	public String encryptToHex(String content, boolean toLowerCase) {
		Assert.isNotEmpty(content);
		return encryptToHex(content.getBytes(), toLowerCase);
	}
	
	@Override
	public byte[] encryptToBase64(byte[] content) {
		Assert.isNotEmpty(content);
		return Base64Utils.encode(doFinal(CipherMode.ENCRYPT, content));
	}

	@Override
	public byte[] encryptToBase64(byte[] content, Base64Type base64Type) {
		Assert.isNotEmpty(content);
		return Base64Utils.encode(doFinal(CipherMode.ENCRYPT, content), base64Type);
	}

	@Override
	public byte[] encryptToBase64(String content) {
		Assert.isNotEmpty(content);
		return Base64Utils.encode(doFinal(CipherMode.ENCRYPT, content.getBytes()));
	}

	@Override
	public byte[] encryptToBase64(String content, Base64Type base64Type) {
		Assert.isNotEmpty(content);
		return encryptToBase64(content.getBytes(), base64Type);
	}

	@Override
	public String encryptToBase64Str(byte[] content) {
		Assert.isNotEmpty(content);
		return Base64Utils.encodeToStr(doFinal(CipherMode.ENCRYPT, content));
	}

	@Override
	public String encryptToBase64Str(byte[] content, Base64Type base64Type) {
		Assert.isNotEmpty(content);
		return Base64Utils.encodeToStr(doFinal(CipherMode.ENCRYPT, content), base64Type);
	}

	@Override
	public String encryptToBase64Str(String content) {
		Assert.isNotEmpty(content);
		return Base64Utils.encodeToStr(doFinal(CipherMode.ENCRYPT, content.getBytes()));
	}

	@Override
	public String encryptToBase64Str(String content, Base64Type base64Type) {
		Assert.isNotEmpty(content);
		return encryptToBase64Str(content.getBytes(), base64Type);
	}

	@Override
	public byte[] decrypt(byte[] content) {
		Assert.isNotEmpty(content);
		return doFinal(CipherMode.DECRYPT, content);
	}
	
	@Override
	public byte[] decryptFromHex(String content) {
		Assert.isNotEmpty(content);
		return doFinal(CipherMode.DECRYPT, HexUtils.decode(content));
	}
	
	@Override
	public String decryptFromHexStr(String content) {
		return StrEncodeUtils.utf8EncodeToStr(decryptFromHex(content));
	}
	
	@Override
	public byte[] decryptFromBase64(byte[] content) {
		Assert.isNotEmpty(content);
		return doFinal(CipherMode.DECRYPT, Base64Utils.decode(content));
	}
	
	@Override
	public byte[] decryptFromBase64(byte[] content, Base64Type base64Type) {
		Assert.isNotEmpty(content);
		return doFinal(CipherMode.DECRYPT, Base64Utils.decode(content, base64Type));
	}
	
	@Override
	public byte[] decryptFromBase64(String content) {
		Assert.isNotEmpty(content);
		return doFinal(CipherMode.DECRYPT, Base64Utils.decode(content));
	}
	
	@Override
	public byte[] decryptFromBase64(String content, Base64Type base64Type) {
		Assert.isNotEmpty(content);
		return doFinal(CipherMode.DECRYPT, Base64Utils.decode(content, base64Type));
	}
	
	@Override
	public String decryptFromBase64Str(byte[] content) {
		return StrEncodeUtils.utf8EncodeToStr(decryptFromBase64(content));
	}
	
	@Override
	public String decryptFromBase64Str(byte[] content, Base64Type base64Type) {
		return StrEncodeUtils.utf8EncodeToStr(decryptFromBase64(content, base64Type));
	}
	
	@Override
	public String decryptFromBase64Str(String content) {
		return StrEncodeUtils.utf8EncodeToStr(decryptFromBase64(content));
	}
	
	@Override
	public String decryptFromBase64Str(String content, Base64Type base64Type) {
		return StrEncodeUtils.utf8EncodeToStr(decryptFromBase64(content, base64Type));
	}
	
	/**
	 * 	默认实现0填充方法
	 * 	<br>如果需要重写，请重写该方法
	 * 	
	 * 	@param 	cipherMode	加密器模式
	 * 	@param 	data		数据
	 * 	@param 	blockSize	指定块大小
	 * 	@return	byte[]
	 */
	public byte[] zeroPadding(CipherMode cipherMode, byte[] data, int blockSize) {
		if (Eq.enums(CipherMode.ENCRYPT, cipherMode)) {
			int paddingLength = blockSize - (data.length % blockSize);
			byte[] paddedData = new byte[data.length + paddingLength];
			System.arraycopy(data, 0, paddedData, 0, data.length);
			return paddedData;
		}
		
		// 去除填充
		int paddingLength = 0;
		for (int i = data.length - 1; i >= 0; i--) {
			if (data[i] != 0) {
				paddingLength = data.length - i - 1;
				break;
			}
		}
		byte[] unpaddingData = new byte[data.length - paddingLength];
		System.arraycopy(data, 0, unpaddingData, 0, unpaddingData.length);
		return unpaddingData;
	}
}
